using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Pinwheel.Toolkits
{
    public partial class ObjectPoolInst<T>
    {
        public delegate Task<T> ObjectCtorAsync();
        private ObjectCtorAsync m_CtorAsync;
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolInst<T> SetAsyncCtor(ObjectCtorAsync ctor)
        {
            m_CtorAsync = ctor;
            return this;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ObjectPoolItemRef CreateItemRefAsync(in int index, ObjectPoolItemType itemType = ObjectPoolItemType.Initialization, int valid = REF_IDLE)
        {
            return new ObjectPoolItemRef(index, valid, default, itemType);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ObjectPoolItemRef GetOrCreateItemRefAsync(ref ObjectPoolItemRef[] itemRefs, in int index, ObjectPoolItemType itemType = ObjectPoolItemType.Initialization, int valid = REF_IDLE)
        {
            if (itemRefs[index] == null)
                itemRefs[index] = CreateItemRefAsync(in index, itemType, valid);
            return itemRefs[index];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetTemporaryItemAsync(ref ObjectPoolItemRef[] itemRefs, ref int index, int valid = ValidTick)
        {
            Profiler.Start("ObjectPoolInst GetTemporaryItemAsync");
            if (index >= itemRefs.Length) index = 0;

            var tickCount = ObjectPoolUtil.GetTickCount();
            var validCount = ObjectPoolUtil.GetTickCount(valid);
            var itemType = ObjectPoolItemType.Initialization;

            ObjectPoolItemRef itemRef = GetOrCreateItemRefAsync(ref itemRefs, in index, itemType);
            if (!itemRef.IsIdle) CorrectItemRef(ref itemRefs, in index, out itemRef);

            if (!itemRef.IsIdle)
            {
                if (itemRefs.Length < m_MaxCount)
                {
                    if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync Resize index->{index}, tickCount->{tickCount}, Valid->{itemRef.Valid}"); 
                    index = itemRefs.Length;
                    ArrayExtensions.CheckArraySize(ref itemRefs, index + 1, m_MaxCount);
                    itemRef = GetOrCreateItemRefAsync(ref itemRefs, in index, itemType, validCount);
                    if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync Resize prev->{index}, cur->{itemRefs.Length}"); 
                }
                else
                {
                    itemRef = null;
                }
            }
            else
            {
                if (null != SetValid) SetValid(itemRef, validCount);
            }
            
            if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync index->{index}, length->{itemRefs.Length}");
            Profiler.End();
            return itemRef;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetPersistentItemAsync(ref ObjectPoolItemRef[] itemRefs, ref int index)
        {
            Profiler.Start("ObjectPoolInst GetPersistentItemAsync");
            var itemType = ObjectPoolItemType.Initialization;

            ObjectPoolItemRef itemRef = null;
            if (index >= itemRefs.Length && index < m_MaxCount)
                ArrayExtensions.CheckArraySize(ref itemRefs, index + 1, m_MaxCount);
            if (index >= 0 && index < itemRefs.Length)
                itemRef = GetOrCreateItemRefAsync(ref itemRefs, in index, itemType, REF_USED);

            Profiler.End();
            return itemRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetOutOfRangeItemAsync(ObjectPoolItemType itemType = ObjectPoolItemType.Temporary, int valid = REF_IDLE)
        {
            Profiler.Start("ObjectPoolInst GetOutOfRangeItemAsync");
            ObjectPoolItemRef itemRef = CreateItemRefAsync(-1, ObjectPoolItemType.OutOfRange, valid);
            if (null != m_Dispose)
            {
                var list = ObjectPoolItemType.Temporary == itemType ? m_TemporaryList : m_PersistentList;
                // if (list.Count >= m_MaxCount + m_MaxCount / 2) Log.Warn($"[{itemType}][{m_MaxCount}][{typeof(T)}]ObjectPoolInst out of range item count[{list.Count}] is too large!");
                var item = new ObjectPoolDisposeItem
                {
                    ItemRef = itemRef,
                    PoolInst = this,
                    Release = m_Dispose,
                };
                
                list.Add(item);
                SetInstId(itemRef, list.Count - 1);
                
                LookupDispose.Add(itemRef, item);
            }
#if DEBUG
            int typeIndex = (int) itemType;
            Log.Error($"ObjectPoolInst<{typeof(T)}> GetOutOfRangeItemAsync instId: [{m_MaxCount}][{m_Counts[typeIndex]}][{m_Indexs[typeIndex]}]{itemRef.InstId}");
#endif
            Profiler.End(); 
            return itemRef;
        }

        public async Task<ObjectPoolItemRef> GetItemRefAsync(ObjectPoolItemType itemType, int valid = ValidTick)
        {
            CheckInitObjectPoolInst();
            CheckTemporaryListValid();

            int typeIndex = (int) itemType;
            ObjectPoolItemRef itemRef = itemType switch
            {
                ObjectPoolItemType.Temporary => GetTemporaryItemAsync(ref m_Items[typeIndex], ref m_Indexs[typeIndex], valid),
                ObjectPoolItemType.Persistent => GetPersistentItemAsync(ref m_Items[typeIndex], ref m_Indexs[typeIndex]),
                _ => null
            };

            if (null != itemRef)
            {
                m_Indexs[typeIndex]++;
                m_Counts[typeIndex] = Math.Max(m_Indexs[typeIndex], m_Counts[typeIndex]);
            }
            else
            {
                itemRef = GetOutOfRangeItemAsync(itemType, itemType == ObjectPoolItemType.Persistent ? REF_USED : valid);
            }

            if (ObjectPoolItemType.Initialization == itemRef.ItemType || ObjectPoolItemType.OutOfRange == itemRef.ItemType)
            {
                if (null != m_CtorAsync)
                {
                    if (null != itemRef.Item && null != m_Dispose) m_Dispose(itemRef.Item);
                    itemRef.Item = await m_CtorAsync();
                    Lookup.Add(itemRef.Item, itemRef);
                }
            
                if (ObjectPoolItemType.Initialization == itemRef.ItemType) 
                    SetItemType(itemRef, itemType); 
            }

            return itemRef;
        }

        public async Task<T> GetItemAsync(int valid)
        {
            CheckInitObjectPoolInst();
            CheckTemporaryListValid();

            var typeIndex = (int) ObjectPoolItemType.Temporary;
            ObjectPoolItemRef itemRef = GetTemporaryItemAsync(ref m_Items[typeIndex], ref m_Indexs[typeIndex], valid);

            if (null != itemRef)
            {
                m_Indexs[typeIndex]++;
                m_Counts[typeIndex] = Math.Max(m_Indexs[typeIndex], m_Counts[typeIndex]);
            }
            else
            {
                itemRef = GetOutOfRangeItemAsync(ObjectPoolItemType.Temporary, valid);
            }
            
            if (ObjectPoolItemType.Initialization == itemRef.ItemType || ObjectPoolItemType.OutOfRange == itemRef.ItemType)
            {
                if (null != m_CtorAsync)
                {
                    if (null != itemRef.Item && null != m_Dispose) m_Dispose(itemRef.Item);
                    itemRef.Item = await m_CtorAsync();
                    Lookup.Add(itemRef.Item, itemRef);
                }
            
                if (ObjectPoolItemType.Initialization == itemRef.ItemType) 
                    SetItemType(itemRef, ObjectPoolItemType.Temporary);
            }

            return itemRef.Item;
        }

        public async Task<T> GetItemAsync() => await GetItemAsync(ValidTick);
    }
    
    public partial class ObjectPoolInst<T, P>
    {
        public delegate Task<T> ObjectCtorAsync(P param);
        private ObjectCtorAsync m_CtorAsync;
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolInst<T, P> SetAsyncCtor(ObjectCtorAsync ctor)
        {
            m_CtorAsync = ctor;
            return this;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ObjectPoolItemRef CreateItemRefAsync(ref P param, in int index, ObjectPoolItemType itemType = ObjectPoolItemType.Persistent, int valid = REF_IDLE)
        {
            return new ObjectPoolItemRef(index, valid, default, itemType);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private ObjectPoolItemRef GetOrCreateItemRefAsync(ref P param, ref ObjectPoolItemRef[] itemRefs, in int index, ObjectPoolItemType itemType = ObjectPoolItemType.Initialization, int valid = REF_IDLE)
        {
            if (itemRefs[index] == null)
                itemRefs[index] = CreateItemRefAsync(ref param, in index, itemType, valid);
            return itemRefs[index];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetTemporaryItemAsync(ref P param, ref ObjectPoolItemRef[] itemRefs, ref int index,
            int valid = ValidTick)
        {
            Profiler.Start("ObjectPoolInst GetTemporaryItemAsync");
            if (index >= itemRefs.Length) index = 0;

            var tickCount = ObjectPoolUtil.GetTickCount();
            var validCount = ObjectPoolUtil.GetTickCount(valid);
            var itemType = ObjectPoolItemType.Initialization;

            ObjectPoolItemRef itemRef = GetOrCreateItemRefAsync(ref param, ref itemRefs, in index, itemType);
            if (!itemRef.IsIdle) CorrectItemRef(ref itemRefs, in index, out itemRef);

            if (!itemRef.IsIdle)
            {
                if (itemRefs.Length < m_MaxCount)
                {
                    if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync Resize index->{index}, tickCount->{tickCount}, Valid->{itemRef.Valid}");
                    index = itemRefs.Length;
                    ArrayExtensions.CheckArraySize(ref itemRefs, index + 1, m_MaxCount);
                    // ArrayExtensions.CheckArraySize(ref m_ItemTicks, index + 1, MaxCount);
                    itemRef = GetOrCreateItemRefAsync(ref param, ref itemRefs, in index, itemType, validCount);
                    if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync Resize prev->{index}, cur->{itemRefs.Length}"); 
                }
                else
                {
                    itemRef = null;
                }
            }
            else
            {
                if (null != SetValid) SetValid(itemRef, validCount);
            }
            if (m_Debug) Log.Info($"ObjectPoolInst GetTemporaryItemAsync index->{index}, length->{itemRefs.Length}");
            Profiler.End();
            return itemRef;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetPersistentItemAsync(ref P param, ref ObjectPoolItemRef[] itemRefs, ref int index)
        {
            Profiler.Start("ObjectPoolInst GetPersistentItemAsync");
            var itemType = ObjectPoolItemType.Initialization;

            ObjectPoolItemRef itemRef = null;
            if (index >= itemRefs.Length && index < m_MaxCount)
                ArrayExtensions.CheckArraySize(ref itemRefs, index + 1, m_MaxCount);
            if (index >= 0 && index < itemRefs.Length)
                itemRef = GetOrCreateItemRefAsync(ref param, ref itemRefs, in index, itemType, REF_USED);
            
            // Log.Info($"GetPersistentItemAsync: index->{index}, length->{itemRefs.Length}");
            Profiler.End();
            return itemRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ObjectPoolItemRef GetOutOfRangeItemAsync(ref P param, ObjectPoolItemType itemType = ObjectPoolItemType.Temporary, int valid = REF_IDLE)
        {
            Profiler.Start("ObjectPoolInst GetOutOfRangeItemAsync");
            ObjectPoolItemRef itemRef = CreateItemRefAsync(ref param, -1, ObjectPoolItemType.OutOfRange, valid);
            if (null != m_Dispose)
            {
                var list = ObjectPoolItemType.Temporary == itemType ? m_TemporaryList : m_PersistentList;
                // if (list.Count >= m_MaxCount + m_MaxCount / 2) Log.Warn($"[{itemType}][{m_MaxCount}][{typeof(T)}]ObjectPoolInst out of range item count[{list.Count}] is too large!");
                var item = new ObjectPoolDisposeItem
                {
                    ItemRef = itemRef,
                    PoolInst = this,
                    Release = m_Dispose,
                    param = param,
                };
                list.Add(item);
                SetInstId(itemRef, list.Count - 1);
                LookupDispose.Add(itemRef, item);
            }
#if DEBUG
            int typeIndex = (int) itemType;
            Log.Error($"ObjectPoolInst<{typeof(T)}, {typeof(P)}> GetOutOfRangeItemAsync instId: [{m_MaxCount}][{m_Counts[typeIndex]}][{m_Indexs[typeIndex]}]{itemRef.InstId}, param: {param}");
#endif
            Profiler.End(); 
            return itemRef;
        }

        public async Task<ObjectPoolItemRef> GetItemRefAsync(P param, ObjectPoolItemType itemType, int valid = ValidTick)
        {
            CheckInitObjectPoolInst();
            CheckTemporaryListValid();

            int typeIndex = (int) itemType;
            ObjectPoolItemRef itemRef = itemType switch
            {
                ObjectPoolItemType.Temporary => GetTemporaryItemAsync(ref param, ref m_Items[typeIndex], ref m_Indexs[typeIndex], valid),
                ObjectPoolItemType.Persistent => GetPersistentItemAsync(ref param, ref m_Items[typeIndex], ref m_Indexs[typeIndex]),
                _ => null
            };
            
            if (null != itemRef)
            {
                m_Indexs[typeIndex]++;
                m_Counts[typeIndex] = Math.Max(m_Indexs[typeIndex], m_Counts[typeIndex]);
            }
            else
            {
                itemRef = GetOutOfRangeItemAsync(ref param, itemType, itemType == ObjectPoolItemType.Persistent ? REF_USED : valid);
            }

            if (ObjectPoolItemType.Initialization == itemRef.ItemType || ObjectPoolItemType.OutOfRange == itemRef.ItemType)
            {
                if (null != m_CtorAsync)
                {
                    if (null != itemRef.Item && null != m_Dispose) m_Dispose(itemRef.Item, param);
                    itemRef.Item = await m_CtorAsync(param); 
                    Lookup.Add(itemRef.Item, itemRef);
                }
            
                if (ObjectPoolItemType.Initialization == itemRef.ItemType) 
                    SetItemType(itemRef, itemType); 
            }

            return itemRef;
        }

        public async Task<T> GetItemAsync(P param, int valid)
        {
            CheckInitObjectPoolInst();
            CheckTemporaryListValid();

            var typeIndex = (int) ObjectPoolItemType.Temporary;
            ObjectPoolItemRef itemRef = GetTemporaryItemAsync(ref param, ref m_Items[typeIndex], ref m_Indexs[typeIndex], valid);

            if (null != itemRef)
            {
                m_Indexs[typeIndex]++;
                m_Counts[typeIndex] = Math.Max(m_Indexs[typeIndex], m_Counts[typeIndex]);
            }
            else
            {
                itemRef = GetOutOfRangeItemAsync(ref param, ObjectPoolItemType.Temporary, valid);
            }
            
            if (ObjectPoolItemType.Initialization == itemRef.ItemType || ObjectPoolItemType.OutOfRange == itemRef.ItemType)
            {
                if (null != m_CtorAsync)
                {
                    if (null != itemRef.Item && null != m_Dispose) m_Dispose(itemRef.Item, param);
                    itemRef.Item = await m_CtorAsync(param);
                    Lookup.Add(itemRef.Item, itemRef);
                }
            
                if (ObjectPoolItemType.Initialization == itemRef.ItemType) 
                    SetItemType(itemRef, ObjectPoolItemType.Temporary); 
            }
            
            return itemRef.Item;
        }

        public async Task<T> GetItemAsync(P param) => await GetItemAsync(param, ValidTick);
    }
}